[SwiftUI] iOS16以降の@EnvironmentObjectとiOS17以降の@Environment

[SwiftUI] iOS16以降の@EnvironmentObjectとiOS17以降の@Environment

Clock Icon2024.11.01

こんにちは。きんくまです。

iOS16までの@EnvironmentObject

つくったもの

https://youtube.com/shorts/vCkOBu3hM24

構造と挙動について

241101-EnvironmentObject

ContentView(親View。EnvironmentObjectを定義)
|- ChildView(子供View。だけどEnvironmentObjectは定義していない)
     |- GrandchildView(孫View。EnvironmentObjectを定義)

通常SwiftUIでは、プロパティを子供に渡して、子供からさらに孫に渡して。
といった感じにバケツリレーでデータを渡します。

@EnvironmentObjectだと、階層にかかわらずデータを渡すことが可能です。

親と孫のデータが共有されているので、以下が行われます

  • 親でのデータ更新が孫のViewも合わせて更新
  • 逆に孫でのデータ更新が親のViewも合わせて更新

ソースコード

class AppSettings: ObservableObject {
    @Published var mainBackgroundColor: Color = .blue
    @Published var isToggleOn: Bool = true
}

struct GrandchildView: View {
    @EnvironmentObject var appSettings: AppSettings

    var body: some View {
        VStack {
            Text("GrandchildView")
            Color(appSettings.mainBackgroundColor)
                .frame(width: 50, height:50)
            Toggle(isOn: $appSettings.isToggleOn) {
                Text("isToggleOn")
            }
            Button {
                appSettings.mainBackgroundColor = .red
            } label: {
                Text("set backgroundColor to red")
            }
        }
        .padding(10)
        .border(Color.gray)
        .frame(width: 300)
    }
}

struct ChildView: View {
    var body: some View {
        VStack {
            Text("ChildView")
            GrandchildView()
        }
        .padding(10)
        .border(Color.gray)
    }
}

struct ContentView: View {
    @StateObject private var appSettings = AppSettings()

    var body: some View {
        VStack {
            Text("ContentView")
            Color(appSettings.mainBackgroundColor)
                .frame(width: 50, height:50)
            Toggle(isOn: $appSettings.isToggleOn) {
                Text("isToggleOn")
            }
            Button {
                appSettings.mainBackgroundColor = .green
            } label: {
                Text("set backgroundColor to green")
            }
            ChildView()
        }
        .padding(10)
        .border(Color.gray)
        .frame(width: 300)
        .environmentObject(appSettings)
    }
}

iOS17以降で同じものを作る

iOS17以降はObservationフレームワークが追加されたので、同じものを書き直します。

@Observable class AppSettings {
    var mainBackgroundColor: Color = .blue
    var isToggleOn: Bool = true
}

struct GrandchildView: View {
    // @EnvironmentObjectから変更
    @Environment(AppSettings.self) private var appSettings: AppSettings

    var body: some View {
        // Toggleで$appSettingsを使いたいので定義
        @Bindable var appSettings = appSettings

        VStack {
            Text("GrandchildView")
            Color(appSettings.mainBackgroundColor)
                .frame(width: 50, height:50)
            Toggle(isOn: $appSettings.isToggleOn) {
                Text("isToggleOn")
            }
            Button {
                appSettings.mainBackgroundColor = .red
            } label: {
                Text("set backgroundColor to red")
            }
        }
        .padding(10)
        .border(Color.gray)
        .frame(width: 300)
    }
}

struct ChildView: View {
    var body: some View {
        VStack {
            Text("ChildView")
            GrandchildView()
        }
        .padding(10)
        .border(Color.gray)
    }
}

struct ContentView: View {
    // @StateObjectから変更
    @State private var appSettings = AppSettings()

    var body: some View {
        VStack {
            Text("ContentView")
            Color(appSettings.mainBackgroundColor)
                .frame(width: 50, height:50)
            Toggle(isOn: $appSettings.isToggleOn) {
                Text("isToggleOn")
            }
            Button {
                appSettings.mainBackgroundColor = .green
            } label: {
                Text("set backgroundColor to green")
            }
            ChildView()
        }
        .padding(10)
        .border(Color.gray)
        .frame(width: 300)
        //.environmentObject(appSettings) から変更
        .environment(appSettings)
    }
}

ポイントは以下です

  • GrandchildViewで@EnvironmentObjectから@Environment(AppSettings.self)に変更
  • ContentViewで@StateObjectから@Stateに変更
  • ContentViewのbodyで、.environmentObject(appSettings) から.environment(appSettings)に変更

@Environmentは今回の使い方だけでなく、キーを指定してデータを共有する使い方もあります。それについては別記事にします。
[SwiftUI] iOS 17以降の@Environmentまとめ

参考

Migrating from the Observable Object protocol to the Observable macro

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.